
#include <dim-sum/errno.h>
#include <dim-sum/err.h>
#include <dim-sum/string.h>
#include <dim-sum/fs.h>
#include <dim-sum/mm_types.h>
#include <dim-sum/beehive.h>
#include <dim-sum/syscall.h>
#include <dim-sum/syscall.h>
#include <dim-sum/bug.h>
#include <dim-sum/sched.h>
#include <dim-sum/elf.h>
#include <dim-sum/exec.h>
#include <dim-sum/mm.h>
#include <asm/current.h>


struct elf_seg_info* alloced_elf_seg_info(void)
{
	struct elf_seg_info* ep;
	ep = (struct elf_seg_info*)kzalloc(sizeof(*ep), PAF_KERNEL);
	if (!ep) {
		return NULL;
	}
	accurate_set(&ep->conut, 1);
	return ep;
}

void free_elf_seg_info(struct elf_seg_info* esi)
{
	BUG_ON(!esi);
	if (accurate_dec_and_test_zero(&esi->conut))
		kfree(esi);
	return;
}

struct elf_phdr *elf_phdr_start(struct elfhdr *elf)
{
	return (struct elf_phdr *)((elf_addr_t)elf + (elf_addr_t)(elf->e_phoff));
}

struct elf_phdr *elf_phdr_addr(struct elfhdr *elf, int indx)
{
	if (elf->e_phoff == 0 || elf->e_phnum == 0 || elf->e_phentsize == 0)
		return NULL;

	return (struct elf_phdr *)(((elf_addr_t)elf + (elf_addr_t)(elf->e_phoff)) +
			 (elf_addr_t)(indx * elf->e_phentsize));
}

static struct elf_phdr *elf_phdr_addr_on_phtbl(struct elfhdr *elf, struct elf_phdr *tbl, int indx)
{
	if (elf->e_phnum == 0 ||
		elf->e_phentsize == 0 || !tbl)
		return NULL;

	return (struct elf_phdr *)((elf_addr_t)tbl +
			 (elf_addr_t)(indx * elf->e_phentsize));
}


struct elf_phdr *elf_shdr_start(struct elfhdr *elf)
{
	return (struct elf_phdr *)((elf_addr_t)elf + (elf_addr_t)(elf->e_phoff));
}

struct elf_shdr *elf_shdr_addr(struct elfhdr *elf, int indx)
{
	if (elf->e_shoff == 0 || elf->e_shnum == 0 || elf->e_shentsize == 0)
		return NULL;

	return (struct elf_shdr *)(((elf_addr_t)elf + (elf_addr_t)(elf->e_shoff)) +
			 (elf_addr_t)(indx * elf->e_shentsize));
}

static struct elf_shdr *elf_shdr_addr_on_shtbl(struct elfhdr *elf, struct elf_shdr *tbl, int indx)
{
	if (elf->e_shnum == 0 ||
		elf->e_shentsize == 0 || !tbl)
		return NULL;

	return (struct elf_shdr *)((elf_addr_t)tbl +
			 (elf_addr_t)(indx * elf->e_shentsize));
}


struct elf_phdr *elf_load_phtbl(int fd, struct elfhdr *elf)
{
	struct elf_phdr *phdr;
	int size, rdsz;
	loff_t pos;
	void *ret;
	int err;

	if (fd < 0 || !elf)
		return NULL;

	size = elf->e_phnum * elf->e_phentsize;
	if (size < (int)elf->e_phentsize)
		return NULL;

	ret = kzalloc(size, PAF_KERNEL);
	if (!ret)
		return NULL;

	err = sys_llseek(fd, 0,
		 (unsigned long)elf->e_phoff, &pos, SEEK_SET);
	if (err) {
		kfree(ret);
		return NULL;
	}

	rdsz = sys_read(fd, ret, size);
	if (rdsz != size) {
		kfree(ret);
		ret = NULL;
	}
	phdr = (struct elf_phdr *)ret;
	return phdr;
}

void elf_free_phtbl(struct elf_phdr *phdr)
{
	if (!phdr)
		return;
	kfree(phdr);
}

struct elf_shdr *elf_load_shtbl(int fd, struct elfhdr *elf)
{
	struct elf_shdr *shdr;
	int size, rdsz;
	loff_t pos;
	void *ret;
	int err;

	if (fd < 0 || !elf)
		return NULL;

	size = elf->e_shnum * elf->e_shentsize;
	if (size < (int)elf->e_shentsize)
		return NULL;

	ret = kzalloc(size, PAF_KERNEL);
	if (!ret)
		return NULL;

	err = sys_llseek(fd, 0,
		 (unsigned long)elf->e_shoff, &pos, SEEK_SET);
	if (err) {
		kfree(ret);
		return NULL;
	}

	rdsz = sys_read(fd, ret, size);
	if (rdsz != size) {
		kfree(ret);
		ret = NULL;
	}
	shdr = (struct elf_shdr *)ret;
	return shdr;
}

void elf_free_shtbl(struct elf_shdr *shdr)
{
	if (!shdr)
		return;
	kfree(shdr);
}

char *elf_load_strsegment(int fd, struct elfhdr *elf, struct elf_shdr *tbl)
{
	struct elf_shdr *shdr;
	int size, rdsz;
	loff_t pos;
	void *ret;
	int err;

	if (fd < 0 || !elf || !tbl)
		return NULL;

	shdr = elf_shdr_addr_on_shtbl(elf, tbl, elf->e_shstrndx);
	if (!shdr)
		return NULL;

	size = shdr->sh_size;

	ret = kzalloc(size, PAF_KERNEL);
	if (!ret)
		return NULL;

	err = sys_llseek(fd, 0,
		 (unsigned long)shdr->sh_offset, &pos, SEEK_SET);
	if (err) {
		kfree(ret);
		return NULL;
	}

	rdsz = sys_read(fd, ret, size);
	if (rdsz != size) {
		kfree(ret);
		ret = NULL;
	}
	return (char *)ret;
}

void elf_free_strsegment(char *segment)
{
	if (!segment)
		return;
	kfree(segment);
}

struct elfhdr *elf_load_elfhdr(int fd)
{
	int size, rdsz;
	loff_t pos;
	void *ret;
	int err;

	if (fd < 0)
		return NULL;

	size = sizeof(struct elfhdr);


	ret = kzalloc(size, PAF_KERNEL);
	if (!ret)
		return NULL;

	err = sys_llseek(fd, 0,
		 (unsigned long)0, &pos, SEEK_SET);
	if (err) {
		kfree(ret);
		return NULL;
	}

	rdsz = sys_read(fd, ret, size);
	if (rdsz != size) {
		kfree(ret);
		ret = NULL;
	}
	return (struct elfhdr *)ret;
}

void elf_free_elfhdr(struct elfhdr *elf)
{
	if (!elf)
		return;
	kfree(elf);
}

/**
 * 计算获取elf文件的加载地址
 */
static int elf_file_loadaddr(struct exec_info *info, struct elfhdr *elf, struct elf_phdr *phdr)
{
	unsigned long entry = 0, loadaddr = -1, tmp, interp_load_addr = 0;
	struct elf_phdr *p = NULL;
	int idx = 0;

	if (!info || !phdr || !elf)
		return -EINVAL;
	for (idx = 0; idx < elf->e_phnum; idx++) {
		p = elf_phdr_addr_on_phtbl(elf, phdr, idx);
		if (p) {
			if (elf_segment_is_load(p) && 
					elf_segment_msize(p)) {
				if (loadaddr == -1) {
					loadaddr = elf_segment_virtaddr(p);
				}

				tmp = elf_segment_virtaddr(p);
				if (tmp) 
					loadaddr = min(loadaddr, tmp);
			}
		}	
	}
	if ((elf_is_exec(elf) && loadaddr == 0) || loadaddr == -1)
		return -EFAULT;
	if (elf_is_dyn(elf) && loadaddr != 0)
		return -EFAULT;
	if (elf_is_dyn(elf)) {
		loadaddr = ELF_PAGESTART(ELF_ET_DYN_BASE);
		interp_load_addr = loadaddr + SZ_1G;
		entry = loadaddr + (unsigned long)(elf->e_entry);
	}
	
	if (elf_is_exec(elf)) {
		entry = elf->e_entry;
	}

	info->entry = entry;
	info->load_addr = loadaddr;
	info->interp_load_addr = interp_load_addr;
	return 0;
}

/**
 * 基于程序头映射
 */
static int elf_base_map_on_phdr(int fd, struct elf_phdr *phdr, virt_addr_t base)
{
	struct vma_desc* vma = NULL;
	u64 acces = PROT_EXT_USER;
	u64 flags = MAP_PRIVATE | MAP_FIXED_NOREPLACE | MAP_FIXED;
	void *start;
	size_t memsz;
	unsigned long off;

	if (!elf_segment_is_load(phdr))
		return 0;
	
	if (base & ~PAGE_MASK)
		return -EINVAL;

	if (!elf_segment_msize(phdr))
		return -EINVAL;

	BUG_ON((phdr->p_vaddr & ~PAGE_MASK) != (phdr->p_offset & ~PAGE_MASK));

	if (elf_segment_is_read(phdr))
		acces |= PROT_READ;

	if (elf_segment_is_write(phdr))
		acces |= PROT_WRITE;

	if (elf_segment_is_exec(phdr))
		acces |= PROT_EXEC;
	
	start = (void *)((elf_segment_virtaddr(phdr) & PAGE_MASK) + base);
	memsz = ((elf_segment_virtaddr(phdr) & (~PAGE_MASK)) + elf_segment_msize(phdr));

	off = phdr->p_offset & PAGE_MASK;


	vma = vmem_mmap_ret_vma(start,
				memsz,
				fd, (off_t)off, acces, flags);
	if (vma == NULL) {
		return -EFAULT;
	}

	if (phdr->p_memsz > phdr->p_filesz) {
		vma->rf_in_v_off = phdr->p_offset;
		vma->rf_in_v_size = phdr->p_filesz;
		smp_mb();
	}

	return 0;
}

int elf_file_preparse(struct exec_info *info, int fd)
{
	struct elf_phdr *phdr = NULL;
	struct elfhdr *elf = NULL;
	int ret = -EFAULT;

	if (!info || fd < 0)
		return -EINVAL;

	elf = elf_load_elfhdr(fd);
	if (!elf)
		return -EFAULT;

	if (!file_is_elf(elf)) {
		ret = -EBADF;
		goto out;
	}

	if (!elf_is_exec(elf) && !elf_is_dyn(elf)) {
		ret = -EBADF;
		goto out;
	}

	info->elf_hdr = elf;

	phdr = elf_load_phtbl(fd, elf);
	if (!phdr) {
		ret = -EFAULT;
		goto out;
	}

	ret = elf_file_loadaddr(info, elf, phdr);
	elf_free_phtbl(phdr);
	return ret;
out:
	if (elf)
		elf_free_elfhdr(elf);
	info->elf_hdr = NULL;
	if (phdr)
		elf_free_phtbl(phdr);
	info->load_addr = 0;
	return ret;
}

/**
 * ELF程序头映射
 */
static int elf_file_map_phdr(struct exec_info *info, int fd, struct elfhdr *elf)
{
	struct elf_phdr *phdr;
	struct elf_phdr *p;
	unsigned long loadaddr = 0;
	int i, map = 0;
	int ret;

	phdr = elf_load_phtbl(fd, elf);
	if (!phdr)
		return -EFAULT;

	ret = elf_file_loadaddr(info, elf, phdr);
	if (ret) 
		return ret;

	for (i = 0; i < elf->e_phnum; i++)
	{
		p = elf_phdr_addr_on_phtbl(elf, phdr, i);
		if (p) {
			if (elf_base_map_on_phdr(fd, p, loadaddr)) {
				ret = -EFAULT;
				goto out;
			}
			map++;

		}
	}

	if (map < 1) {
		ret = -EFAULT;
		goto out;
	}

	/**
	 * 设置应用程序入口
	 */
	current->task_main = (void *)elf->e_entry;
	ret = 0;
out:
	if (ret == 0) {
		info->exe_phdr = phdr;
		info->phdr_nr = elf->e_phnum;
	} else {
		elf_free_phtbl(phdr);
	}

	return ret;
}


static int elf_file_exec_map(struct exec_info *info, int fd, struct elfhdr *elf)
{
	if (!elf_is_exec(elf))
		return -EFAULT;
	
	return elf_file_map_phdr(info, fd, elf);
}



/**
 * ELF文件映射
 */
static int elf_file_map(struct exec_info *info, int fd, struct elfhdr *elf)
{
	if (elf_is_exec(elf) && !elf_is_dyn(elf)) {
		return elf_file_exec_map(info, fd, elf);
	}

	return -EFAULT;
}

/**
 * 加载ELF文件运行
 */
int elf_file_execve(struct exec_info *info, int fd, char * const *argv,
				char * const *envp)
{
	struct elfhdr *elf;
	int ret;
	if (!info)
		return -EINVAL;
	if (fd < 0)
		return -EBADFD;
	elf = elf_load_elfhdr(fd);
	if (!elf)
		return -EFAULT;
	if (!file_is_elf(elf)) {
		elf_free_elfhdr(elf);
		return -EBADF;
	}
	if (!elf_is_exec(elf) && !elf_is_dyn(elf)) {
		elf_free_elfhdr(elf);
		return -EBADF;
	}
	if (!elf_is_arch(elf)) {
		elf_free_elfhdr(elf);
		return -EBADF;
	}

	ret = elf_file_map(info, fd, elf);
	if (ret) {
		elf_free_elfhdr(elf);
		return ret;
	}

	if (info->elf_hdr)
		elf_free_elfhdr(elf);
	else
		info->elf_hdr = elf;
	return 0;
}

